using System;
using System.Collections.Generic;
using Sustainalytics.Admin.Synchronization.Utility;
using Sustainalytics.ESGRatings.DataContract.ESG;
using System.Linq;
using Sustainalytics.ClientDW.Entities;
using System.Collections.Concurrent;
using System.Diagnostics;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESG.Entities;
using Sustainalytics.ESGRatings.BusinessLogic;
using Sustainalytics.Utils;

namespace Sustainalytics.ReportGenerator.BL
{
    public class ESGRatingsAdapter
    {

        private const double DefaultMarketplaceCapitalization = -1;

        private ESGRatingsDto originalData;

        private ConcurrentDictionary<int, FinancialData> _financialDataOrderedByMarket;
        private ConcurrentDictionary<Guid, int> _indexCompanyToMarketPosion;

        private ConcurrentDictionary<Guid, CompanyRatingDto> _companiesScoresIndex = new ConcurrentDictionary<Guid, CompanyRatingDto>();
        private ConcurrentDictionary<int, GroupRatingDto> _groupIndex = new ConcurrentDictionary<int, GroupRatingDto>();
        private ConcurrentDictionary<int, int> _groupsStartIndex;
        private ConcurrentBag<KeyValuePair<int, string>> _themes = new ConcurrentBag<KeyValuePair<int, string>>();


        #region indicators index

        private ConcurrentDictionary<KeyValuePair<string, int>, IndicatorGroupStruct> _indicatorsAggregates = new ConcurrentDictionary<KeyValuePair<string, int>, IndicatorGroupStruct>();
        private object _syncIndicatorAggregates = new object();

        private ConcurrentDictionary<Guid, IndicatorDataStruct[]> _indexIndicators;
        private ConcurrentDictionary<string, int> _indexIndicatorsCodes;
        #endregion

        #region clusters index
        private int AllIndex = -1;
        private int EIndex = -1;
        private int SIndex = -1;
        private int GIndex = -1;
        private int PreparednessIndex = -1;
        private int QualitativePerformanceIndex = -1;
        private int QuantitativePerformanceIndex = -1;
        private int DisclosureScoreIndex = -1;
        private Dictionary<Guid, FinancialData> _financialData;
        private Dictionary<Guid, List<EsgFocusThresholds>> _companyEsgthresholds;
        private readonly Guid _setId;

        #endregion

        public ESGRatingsAdapter(ESGRatingsDto data)
        {
            Guard.ArgumentNotNull(data, "data");
            originalData = data;
            var builder = new ConcurrentDictionary<int, int>();

            _setId = data.SetId;

            if (data.Ratings != null)
            {
                var previousGroupId = -1;
                var index = -1;
                foreach (var cr in data.Ratings)
                {
                    ++index;
                    _companiesScoresIndex[cr.CompanyId] = cr;
                    int currentGroupId = cr.GroupId;
                    if (currentGroupId != previousGroupId)
                    {
                        builder.TryAdd(cr.GroupId, index);
                        previousGroupId = currentGroupId;
                    }
                }
            }

            _groupsStartIndex = new ConcurrentDictionary<int, int>(builder);

            if (data.Groups != null)
                foreach (var cr in data.Groups)
                {
                    if (cr == null)
                        continue;
                    _groupIndex[cr.GroupId] = cr;
                }

            if (data.IndicatorsGroups != null && data.IndicatorsGroups.All != null)
                AllIndex = data.IndicatorsGroups.All.Id;
            else
                AllIndex = -1;

            BuidIndicatorsIndex(data);

            EIndex = GetThemeIndex(data, "E");
            SIndex = GetThemeIndex(data, "S");
            GIndex = GetThemeIndex(data, "G");

            QualitativePerformanceIndex = GetClusterIndex(data, "QLP");
            QuantitativePerformanceIndex = GetClusterIndex(data, "QP");
            PreparednessIndex = GetClusterIndex(data, "P");
            DisclosureScoreIndex = GetClusterIndex(data, "D");


            _themes.Add(new KeyValuePair<int, string>(EIndex, "Environment"));
            _themes.Add(new KeyValuePair<int, string>(SIndex, "Social"));
            _themes.Add(new KeyValuePair<int, string>(GIndex, "Governance"));

        }

        private void BuidIndicatorsIndex(ESGRatingsDto data)
        {
            if (data.Indicators == null || data.IndicatorsCodes == null)
                return;

            Stopwatch watch = new Stopwatch();
            watch.Start();

            var indicatorIndexBuilder = new ConcurrentDictionary<Guid, IndicatorDataStruct[]>();
            var codesBuilder = new ConcurrentDictionary<string, int>();


            int indicatorsCount = data.IndicatorsCodes.Length;
            for (int i = 0; i < indicatorsCount; i++)
            {
                codesBuilder.TryAdd(data.IndicatorsCodes[i].ToLowerInvariant(), i);
            }

            foreach (var companyIndicators in data.Indicators)
            {
                var array = new IndicatorDataStruct[indicatorsCount];

                if (companyIndicators != null && companyIndicators.Indicators != null)
                {
                    indicatorIndexBuilder.TryAdd(companyIndicators.CompanyId, array);

                    foreach (var indicator in companyIndicators.Indicators)
                    {
                        array[indicator.Index] = new IndicatorDataStruct
                        {
                            IsNotNan = 1,
                            Score = indicator.Score,
                            WeightedScore = indicator.WeightedScore,
                            Weight = indicator.Weight
                        };
                    }
                }
            }

            _indexIndicatorsCodes = codesBuilder;
            _indexIndicators = indicatorIndexBuilder;

            watch.Stop();
        }

        private static int GetThemeIndex(ESGRatingsDto data, string themeName)
        {
            if (data == null || data.IndicatorsGroups == null)
            {
                return -1;
            }

            var cluster = data.IndicatorsGroups.Themes.FirstOrDefault(i => String.Compare(i.Name, themeName, StringComparison.OrdinalIgnoreCase) == 0);
            if (cluster != null)
                return cluster.Id;

            return -1;
        }

        private static int GetClusterIndex(ESGRatingsDto data, string clusterName)
        {
            if (data == null || data.IndicatorsGroups == null)
            {
                return -1;
            }

            var cluster = data.IndicatorsGroups.Clusters.FirstOrDefault(i => String.Compare(i.Name, clusterName, StringComparison.OrdinalIgnoreCase) == 0);
            if (cluster != null)
                return cluster.Id;

            return -1;
        }

        public Guid SetId
        {
            get { return _setId; }
        }

        public ESGData GetEsgData(Guid companyId)
        {
            CompanyRatingDto companyScore;
            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                return null;

            var esgData = new ESGData();

            esgData.Score = Convert.ToSingle(Math.Round(GetRating(companyScore, AllIndex), 4, MidpointRounding.AwayFromZero));

            esgData.IsInReferenceUniverse = GetIsInReferenceUniverse(companyScore);
            esgData.GroupAvg = GetGroupAvg(companyScore.GroupId, AllIndex);
            esgData.Rank = GetRank(companyScore, AllIndex);
            esgData.Percentile = GetPercentile(companyScore, AllIndex);
            esgData.GroupCount = GetGroupCount(companyScore, AllIndex);


            esgData.EnvironmentScore = Convert.ToSingle(Math.Round(GetRating(companyScore, EIndex), 4, MidpointRounding.AwayFromZero));
            esgData.SocialScore = Convert.ToSingle(Math.Round(GetRating(companyScore, SIndex), 4, MidpointRounding.AwayFromZero));
            esgData.GovernanceScore = Convert.ToSingle(Math.Round(GetRating(companyScore, GIndex), 4, MidpointRounding.AwayFromZero));



            esgData.EnvironmentRank = GetRank(companyScore, EIndex);
            esgData.SocialRank = GetRank(companyScore, SIndex);
            esgData.GovernanceRank = GetRank(companyScore, GIndex);


            esgData.EnvironmentPercentile = GetPercentile(companyScore, EIndex);
            esgData.SocialPercentile = GetPercentile(companyScore, SIndex);
            esgData.GovernancePercentile = GetPercentile(companyScore, GIndex);



            esgData.PreparednessScore = Convert.ToSingle(Math.Round(GetRating(companyScore, PreparednessIndex), 4, MidpointRounding.AwayFromZero));
            esgData.QualitativePerformanceScore = Convert.ToSingle(Math.Round(GetRating(companyScore, QualitativePerformanceIndex), 4, MidpointRounding.AwayFromZero));
            esgData.QuantitativePerformanceScore = Convert.ToSingle(Math.Round(GetRating(companyScore, QuantitativePerformanceIndex), 4, MidpointRounding.AwayFromZero));
            esgData.DisclosureScore = Convert.ToSingle(Math.Round(GetRating(companyScore, DisclosureScoreIndex), 4, MidpointRounding.AwayFromZero));

            return esgData;
        }

        private static bool GetIsInReferenceUniverse(CompanyRatingDto companyScore)
        {

            return companyScore.IsInReferenceUniverse;

        }

        public ESGBestInClass GetBestInClass(Guid companyId)
        {
            CompanyRatingDto companyScore;
            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                return null;

            return new ESGBestInClass
            {
                OverallBestInClass = GetGroupMax(companyScore.GroupId, AllIndex),
                EBestInClass = GetGroupMax(companyScore.GroupId, EIndex),
                GBestInClass = GetGroupMax(companyScore.GroupId, GIndex),
                SBestInClass = GetGroupMax(companyScore.GroupId, SIndex),
                DisclosureMaxInGroup = GetGroupMax(companyScore.GroupId, DisclosureScoreIndex),
                PreparednessMaxInGroup = GetGroupMax(companyScore.GroupId, PreparednessIndex),
                QualitativePerformanceMaxInGroup = GetGroupMax(companyScore.GroupId, QualitativePerformanceIndex),
                QuantitativePerformanceMaxInGroup = GetGroupMax(companyScore.GroupId, QuantitativePerformanceIndex),
            };
        }

        public ESGAvg GetESGAvg(Guid companyId)
        {
            CompanyRatingDto companyScore;
            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                return null;

            return new ESGAvg
            {
                OverallAvg = GetGroupAvg(companyScore.GroupId, AllIndex),
                EAvg = GetGroupAvg(companyScore.GroupId, EIndex),
                GAvg = GetGroupAvg(companyScore.GroupId, GIndex),
                SAvg = GetGroupAvg(companyScore.GroupId, SIndex),
            };
        }

        // used single threaded
        public int LoadFinancialData(Dictionary<Guid, FinancialData> financialData)
        {
            _financialData = financialData;

            var sorted = financialData
                .Where(kvp => GetRating(kvp.Key, AllIndex) > 0)
                .OrderByDescending(
                    kvp => new KeyValuePair<int, double>(GetGroup(kvp.Key), (double?)kvp.Value.MarketCapitalization ?? -1),
                    new CustomComparer());

            int index = 0;
            var financialBuilder = new ConcurrentDictionary<int, FinancialData>();
            var positionIndexBuilder = new ConcurrentDictionary<Guid, int>();
            foreach (var item in sorted)
            {
                positionIndexBuilder.TryAdd(item.Key, index);
                financialBuilder.TryAdd(index, item.Value);
                index++;
            }

            _financialDataOrderedByMarket = financialBuilder;
            _indexCompanyToMarketPosion = positionIndexBuilder;

            return index;

        }

        public void LoadEsgFocus(Dictionary<Guid, List<EsgFocusThresholds>> esgThresholds)
        {
            _companyEsgthresholds = esgThresholds;
        }


        public Tuple<double, double> ComputeMarketCapitalization(ICollection<CompanyScore> industryPeers)
        {
            double min = 0;
            double max = 0;

            if (industryPeers != null && industryPeers.Any())
            {
                var minMarkerCap = industryPeers.Min(p => p.MarketCapitalization);
                var maxMarkerCap = industryPeers.Max(p => p.MarketCapitalization);

                if (minMarkerCap > 0)
                {
                    min = minMarkerCap / 1000;
                }

                if (maxMarkerCap > 0)
                {
                    max = maxMarkerCap / 1000;
                }
            }

            return new Tuple<double, double>(min, max);
        }

        public List<CompanyScore> ComparativeGroupTop5ByCompanyId(Guid companyId, int index = 0)
        {
            int top = 5;
            List<CompanyScore> list = new List<CompanyScore>(top);

            CompanyRatingDto companyScore;
            if (_companiesScoresIndex == null)
                return list;

            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                return list;

            if (companyScore == null)
                return list;

            int groupId = companyScore.GroupId;

            GroupRatingDto groupInfo;
            if (!_groupIndex.TryGetValue(groupId, out groupInfo))
            {
                return list;
            }

            if (!groupInfo.TopCompaniesScoresOnComparativeGroup.IsNullOrEmpty())
            {
                var itemsToAdd = groupInfo.TopCompaniesScoresOnComparativeGroup.Where(item => item.Index == index).ToList();
                var items = from companyInfo in itemsToAdd
                            join financialInfo in _financialData
                                on companyInfo.CompanyId equals financialInfo.Key
                            select new CompanyScore()
                            {
                                CompanyId = companyInfo.CompanyId,
                                Score = companyInfo.Score,
                                Rank = companyInfo.Rank,
                                CompanyName = financialInfo.Value.CompanyName
                            };



                list.AddRange(items);

            }
            else
            {
                int startIndex;
                if (!_groupsStartIndex.TryGetValue(groupId, out startIndex))
                {
                    return list;
                }

                var found = 0;
                var i = startIndex;
                while (found < top && i <= originalData.Ratings.Count - 1)
                {
                    var topScore = originalData.Ratings[i];


                    if (topScore.GroupId != groupId)
                    {
                        break;
                    }

                    CompanyScore score = null;
                    if (topScore.CompanyId == companyId)
                    {
                        score = new CompanyScore { CompanyId = companyScore.CompanyId, Score = companyScore.ClustersRatings[0], Rank = companyScore.ClustersAggregates[0].Rank };
                    }
                    else
                    {
                        var slot = GetSlot(topScore.CompanyId, AllIndex);

                        if (!slot.Equals(default(ESGSlot)) && slot.IsInReferenceUniverse)
                        {
                            score = new CompanyScore { CompanyId = topScore.CompanyId, Score = slot.Score, Rank = slot.Rank };
                        }
                    }

                    if (score != null)
                    {
                        FinancialData financialData;
                        if (_financialData.TryGetValue(score.CompanyId, out financialData))
                        {
                            score.CompanyName = financialData.CompanyName;
                        }
                        ++found;
                        list.Add(score);
                    }

                    ++i;
                }

            }

            return list;
        }

        public List<CompanyScore> GetIndustryPeers(Guid companyId)
        {
            int maxElements = 5;
            int marketIndex;

            if (_indexCompanyToMarketPosion == null)
                return new List<CompanyScore>(0);

            if (!_indexCompanyToMarketPosion.TryGetValue(companyId, out marketIndex))
                return new List<CompanyScore>(0);

            var list = new List<CompanyScore>(maxElements);

            CompanyRatingDto companyScore;
            if (_companiesScoresIndex == null)
                return list;

            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                return list;

            if (companyScore == null)
                return list;

            int groupId = companyScore.GroupId;

            GroupRatingDto groupInfo;
            if (!_groupIndex.TryGetValue(groupId, out groupInfo))
            {
                return list;
            }

            var currentCompanyScore = GetCompanyScoreByMarket(marketIndex, groupId, 0);
            if (currentCompanyScore != null)
            {
                list.Add(currentCompanyScore);
            }

            int[] directions = { 1, -1 };
            byte found = 1;

            var groupCount = GetGroupCount(companyScore, AllIndex);
            if (groupCount < maxElements)
                maxElements = groupCount;

            int offset = 1;
            while (found < maxElements)
            {
                foreach (var direction in directions)
                {
                    var marketData = this.GetCompanyScoreByMarket(marketIndex, groupId, offset * direction);
                    if (marketData != null)
                    {
                        list.Add(marketData);
                        ++found;
                    }

                    if (found == maxElements)
                        break;
                }

                ++offset;

                if (offset > groupCount)
                {
                    break;
                }
            }

            return list.OrderByDescending(c => c.Score).ToList();

        }

        private int GetGroupCount(CompanyRatingDto companyScore, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;

            try
            {
                return companyScore.ClustersAggregates[clusterIndex].Count;
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private float GetGroupAvg(int groupId, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;

            GroupRatingDto grp;
            if (!_groupIndex.TryGetValue(groupId, out grp))
            {
                return -1;
            }
            try
            {
                return grp.ClusterAggregates[clusterIndex].Avg;
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private float GetGroupMax(int groupId, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;

            GroupRatingDto grp;
            if (!_groupIndex.TryGetValue(groupId, out grp))
            {
                return -1;
            }
            try
            {
                return grp.ClusterAggregates[clusterIndex].Max;
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private float GetGroupMin(int groupId, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;

            GroupRatingDto grp;
            if (!_groupIndex.TryGetValue(groupId, out grp))
            {
                return -1;
            }
            try
            {
                return grp.ClusterAggregates[clusterIndex].Min;
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private int GetRank(CompanyRatingDto companyScore, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;
            try
            {
                return companyScore.ClustersAggregates[clusterIndex].Rank;
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private float GetPercentile(CompanyRatingDto companyScore, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;
            try
            {
                return companyScore.ClustersAggregates[clusterIndex].Percentile;

            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private float GetAggregatedWeight(CompanyRatingDto companyScore, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;
            try
            {
                return companyScore.ClustersWeights[clusterIndex];
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private float GetRating(CompanyRatingDto companyScore, int clusterIndex)
        {
            if (clusterIndex < 0)
                return -1;
            try
            {
                return companyScore.ClustersRatings[clusterIndex];
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private CompanyScore GetCompanyScoreByMarket(int marketIndex, int group, int offset)
        {
            try
            {
                var newIndex = marketIndex + offset;
                if ((newIndex < 0) || (newIndex >= _financialDataOrderedByMarket.Count))
                    return null;

                var financialData = _financialDataOrderedByMarket[newIndex];
                var newGroup = GetGroup(financialData.Id);
                var slot = GetSlot(financialData.Id, AllIndex);

                if (@group != newGroup || slot.Equals(default(ESGSlot)) || (!slot.IsInReferenceUniverse && offset != 0))
                {
                    return null;
                }
                var companyScore = new CompanyScore
                {
                    CompanyId = financialData.Id,
                    CompanyName = financialData.CompanyName,
                    MarketCapitalization = financialData.MarketCapitalization ?? DefaultMarketplaceCapitalization,
                    Rank = slot.Rank,
                    Score = slot.Score,
                };

                return companyScore;
            }
            catch (ArgumentOutOfRangeException)
            {
                return null;
            }
            catch (IndexOutOfRangeException)
            {
                return null;
            }

        }

        private ESGSlot GetSlot(Guid companyId, int clusterIndex)
        {
            CompanyRatingDto companyScore;
            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                return new ESGSlot();


            return new ESGSlot
            {
                Score = GetRating(companyScore, clusterIndex),
                Rank = GetRank(companyScore, clusterIndex),
                IsInReferenceUniverse = companyScore.IsInReferenceUniverse
            };

        }

        private float GetRating(Guid companyId, int clusterIndex)
        {
            try
            {
                CompanyRatingDto companyScore;
                if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                    return -1;

                return companyScore.ClustersRatings[clusterIndex];
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }
        }

        private int GetGroup(Guid companyId)
        {
            CompanyRatingDto companyScore;
            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore))
                return -1;

            return companyScore.GroupId;
        }

        public  RPIndicatorPerformance[] GetCompanyIndicatorsPerformance(Guid companyId)
        {
            var  indicatorData = GetCompanyIndicatorData(companyId);
            return indicatorData.Select(x => new RPIndicatorPerformance
            {
                Code = x.Key.ToUpperInvariant(),
                RawScore = x.Value.Score,
                Weight = x.Value.Weight,
                WeightedScore = x.Value.WeightedScore
            }).ToArray();
        }

        public Dictionary<string, IndicatorDataStruct> GetCompanyIndicatorData(Guid companyId)
        {
            Dictionary<string, IndicatorDataStruct> dict = new Dictionary<string, IndicatorDataStruct>();
            if (_indexIndicators.ContainsKey(companyId))
            {
                var indicatorsData = _indexIndicators[companyId];
                int i = 0;
                foreach (var indicator in indicatorsData)
                {
                    if (indicator.IsNotNan == 1)
                    {

                        dict.Add(originalData.IndicatorsCodes[i].ToLowerInvariant(), indicator);
                    }
                    ++i;
                }
            }
            return dict;
        }

        public OverallPerformance GetCompanyPerformance(Guid companyId)
        {
            var esgData = GetEsgData(companyId);

            var companyPerformance = new OverallPerformance();

            if (esgData != null)
            {
                companyPerformance.CompanyScore = (int)Math.Round(esgData.Score, MidpointRounding.AwayFromZero);
                companyPerformance.CompanyCount = esgData.GroupCount;

                companyPerformance.CompanyRank = esgData.Rank;
                companyPerformance.Percentile = esgData.Percentile;

                companyPerformance.IsInReferenceUniverse = esgData.IsInReferenceUniverse;

                companyPerformance.EnvironmentScore = (int)Math.Round(esgData.EnvironmentScore, MidpointRounding.AwayFromZero);
                companyPerformance.SocialScore = (int)Math.Round(esgData.SocialScore, MidpointRounding.AwayFromZero);
                companyPerformance.GovernanceScore = (int)Math.Round(esgData.GovernanceScore, MidpointRounding.AwayFromZero);


                companyPerformance.EnvironmentRank = esgData.EnvironmentRank;
                companyPerformance.EnvironmentPercentile = esgData.EnvironmentPercentile;


                companyPerformance.SocialRank = esgData.SocialRank;
                companyPerformance.SocialPercentile = esgData.SocialPercentile;

                companyPerformance.GovernanceRank = esgData.GovernanceRank;
                companyPerformance.GovernancePercentile = esgData.GovernancePercentile;

                var companyTrsholds = _companyEsgthresholds[companyId].ToArray();

                var companyAssesment = FocusedRatingsAssessmentManager.GetAssessment(esgData.Score, companyTrsholds);
                var envAssesment = FocusedRatingsAssessmentManager.GetAssessment(esgData.EnvironmentScore, companyTrsholds, ThresholdType.Environment);
                var socialAssesment = FocusedRatingsAssessmentManager.GetAssessment(esgData.SocialScore, companyTrsholds, ThresholdType.Social);
                var governanceAssesment = FocusedRatingsAssessmentManager.GetAssessment(esgData.GovernanceScore, companyTrsholds, ThresholdType.Governance);

                companyPerformance.CompanyAssessment = new RPAssessment { Id = companyAssesment.Id, Name = companyAssesment.Name };

                companyPerformance.EnvironmentAssessment = new RPAssessment { Id = envAssesment.Id, Name = envAssesment.Name };

                companyPerformance.SocialAssessment = new RPAssessment { Id = socialAssesment.Id, Name = socialAssesment.Name };

                companyPerformance.GovernanceAssessment = new RPAssessment { Id = governanceAssesment.Id, Name = governanceAssesment.Name };

            }
            else
            {
                throw new ArgumentNullException(nameof(companyId), $"ESG data not found for company {companyId}");
            }
            return companyPerformance;

        }

        public RPPerformance GetEnvironmentPerformance(Guid companyid)
        {
            return GetRpPerformance(companyid, EIndex);
        }

        public IEnumerable<Guid> GetCompaniesInGroup(Guid groupId)
        {
            int groupStartIndex = GetGroup(groupId);
            return EnumerateCompaniesInGroup(groupStartIndex);

        }


        public RPPerformance GetOverallPerformance(Guid companyid)
        {
            return GetRpPerformance(companyid, AllIndex);
        }

        public RPPerformance GetGovernancePerformance(Guid companyid)
        {
            return GetRpPerformance(companyid, GIndex);
        }


        public RPPerformance GetSocialPerformance(Guid companyId)
        {
            return GetRpPerformance(companyId, SIndex);
        }



        public RPPerformance GetPreparednessPerformance(Guid companyId)
        {
            return GetRpPerformance(companyId, PreparednessIndex);
        }



        public RPPerformance GetDisclosurePerformance(Guid companyId)
        {
            return GetRpPerformance(companyId, DisclosureScoreIndex);
        }


        public RPPerformance GetQualitativePerformance(Guid companyId)
        {
            return GetRpPerformance(companyId, QualitativePerformanceIndex);
        }


        public RPPerformance GetQuantitativePerformance(Guid companyId)
        {
            return GetRpPerformance(companyId, QuantitativePerformanceIndex);
        }

        private ThresholdType GetAssessmentType(int clusterIndex)
        {
            return clusterIndex == EIndex
                   ? ThresholdType.Environment
                   : clusterIndex == SIndex
                       ? ThresholdType.Social
                       : clusterIndex == GIndex
                           ? ThresholdType.Governance
                           : ThresholdType.Overall;
        }

        private RPPerformance GetRpPerformance(Guid companyId, int clusterIndex)
        {
            RPPerformance rpPerformance = new RPPerformance();
            //ToDO: extend to obtain proper assesment


            CompanyRatingDto companyScore;
            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore) || companyScore == null)
                return new RPPerformance();


            rpPerformance.Percentile = GetPercentile(companyScore, clusterIndex);
            rpPerformance.GroupCount = GetGroupCount(companyScore, clusterIndex);
            var score = GetRating(companyScore, clusterIndex);
            rpPerformance.Score = (float)Math.Round(score, 4, MidpointRounding.AwayFromZero);

            rpPerformance.Rank = GetRank(companyScore, clusterIndex);
            rpPerformance.Percentile = GetPercentile(companyScore, clusterIndex);


            var assessment = FocusedRatingsAssessmentManager.GetAssessment(rpPerformance.Score, _companyEsgthresholds[companyId].ToArray(), GetAssessmentType(clusterIndex));
            rpPerformance.Assessment = assessment;

            rpPerformance.Weight = (float)Math.Round(GetAggregatedWeight(companyScore, clusterIndex), 4, MidpointRounding.AwayFromZero);
            rpPerformance.Avg = (float)Math.Round(GetGroupAvg(companyScore.GroupId, clusterIndex), 4, MidpointRounding.AwayFromZero);
            rpPerformance.Max = (float)Math.Round(GetGroupMax(companyScore.GroupId, clusterIndex), 4, MidpointRounding.AwayFromZero);
            rpPerformance.Min = (float)Math.Round(GetGroupMin(companyScore.GroupId, clusterIndex), 4, MidpointRounding.AwayFromZero);

            return rpPerformance;
        }

        public List<ThemePerformance> GetThemesPerformance(Guid companyId)
        {
            CompanyRatingDto companyScore;
            if (!_companiesScoresIndex.TryGetValue(companyId, out companyScore) || companyScore == null)
                return new List<ThemePerformance>(0);

            var list = new List<ThemePerformance>(_themes.Count);
            var groupCount = GetGroupCount(companyScore, AllIndex);
            foreach (var item in _themes)
            {

                var performace = new ThemePerformance();
                performace.ThemeName = item.Value;

                performace.Count = groupCount;
                var score = GetRating(companyScore, item.Key);
                //ToDO: is this int?
                performace.Score = (int)Math.Round(score, MidpointRounding.AwayFromZero);

                performace.Rank = GetRank(companyScore, item.Key);
                performace.Percentile = GetPercentile(companyScore, item.Key);

                var thresholdType = item.Value == "Environment"
                    ? ThresholdType.Environment
                    : item.Value == "Social"
                        ? ThresholdType.Social
                        : item.Value == "Governance"
                            ? ThresholdType.Governance
                            : ThresholdType.Overall;

                var assesmnet = FocusedRatingsAssessmentManager.GetAssessment(performace.Score,
                    _companyEsgthresholds[companyId].ToArray(), thresholdType);

                performace.Assessment = assesmnet.Name;

                performace.Weight = (float)Math.Round(GetAggregatedWeight(companyScore, item.Key), 4, MidpointRounding.AwayFromZero);
                performace.IsInReferenceUniverse = companyScore.IsInReferenceUniverse;

                list.Add(performace);
            }

            return list;
        }

        public bool FillIndicatorsAggregates(Guid companyId, KeyEsgIssueIndicator indicator)
        {
            Guard.ArgumentNotNull(indicator, "indicator");

            if (indicator.Type != KeyEsgIssueIndicatorType.StandardIndicator)
                return false;

            if (String.IsNullOrEmpty(indicator.Number))
                return false;

            var groupId = GetGroup(companyId);
            if (groupId < 0)
                return false;

            string lowerIndicatorNumber = indicator.Number.ToLowerInvariant();

            IndicatorGroupStruct aggregates;
            var key = new KeyValuePair<string, int>(lowerIndicatorNumber, groupId);
            if (!_indicatorsAggregates.TryGetValue(key, out aggregates))
            {
                lock (_syncIndicatorAggregates)
                {
                    aggregates = CreateIndicatorsAggregates(lowerIndicatorNumber, groupId);
                    _indicatorsAggregates[key] = aggregates;
                }
            }

            var score = GetIndicatorScore(lowerIndicatorNumber, companyId);

            indicator.Average = GetScoreStringByValue(aggregates.GroupAvg);
            indicator.BestPractice = GetScoreStringByValue(aggregates.BestPractice);
            indicator.Score = GetScoreStringByValue(score);

            var gap = GetGap(score, aggregates.BestPractice);

            indicator.MaterialBestPracticeGap = gap;
            return true;

        }

        private static int GetGap(float score, float bestPractice)
        {
            var gap = 0;
            if (score >= 0 && bestPractice >= 0)
            {
                var diff = bestPractice - score; //as required by Andreea Aldea

                if (diff < 25)
                {
                    gap = 1;
                }
                else if (diff < 75)
                {
                    gap = 2;
                }
                else
                {
                    gap = 3;
                }
            }
            return gap;
        }

        private string GetScoreStringByValue(float aggregates)
        {
            const double tolerance = 1e-16;
            return Math.Abs(aggregates - (-1)) < tolerance ? "N/A" : string.Format("{0}", Math.Round(aggregates, MidpointRounding.AwayFromZero));
        }

        private IndicatorGroupStruct CreateIndicatorsAggregates(string lowerIndicatorNumber, int groupId)
        {
            var collection = EnumerateCompaniesInGroup(groupId)
                            .Select(ci => GetIndicatorScore(lowerIndicatorNumber, ci))
                            .Where(score => score > -1).OrderByDescending(score => score).ToList();


            var count = collection.Count;
            if (count == 0)
            {
                return new IndicatorGroupStruct { BestPractice = -1, GroupAvg = -1 };
            }

            int topPercent = (int)Math.Ceiling(count / 4.0f);
            double allSum = 0;
            double topSum = 0;

            var index = 0;
            foreach (var score in collection)
            {
                if (index < topPercent)
                {
                    topSum += score;
                }

                allSum += score;
                index++;
            }

            if (topPercent != 0)
                topSum = topSum / topPercent;

            allSum = allSum / count;

            return new IndicatorGroupStruct { BestPractice = (float)topSum, GroupAvg = (float)allSum };

        }

        private IEnumerable<Guid> EnumerateCompaniesInGroup(int groupId)
        {
            int groupStartIndex;
            if (!_groupsStartIndex.TryGetValue(groupId, out groupStartIndex))
            {
                yield break;
            }
            var ratings = originalData.Ratings;
            int maxLen = ratings.Count;

            for (int i = groupStartIndex; i < maxLen; i++)
            {
                var current = ratings[i];

                if (current.GroupId != groupId)
                    yield break;

                yield return current.CompanyId;
            }
        }

        private float GetIndicatorScore(string lowerIndicatorNumber, Guid companyId)
        {

            int index = GetIndicatorIndex(lowerIndicatorNumber);
            if (index < 0)
                return -1;

            try
            {
                IndicatorDataStruct[] values;
                if (_indexIndicators.TryGetValue(companyId, out values))
                {
                    var value = values[index];
                    if (value.IsNotNan == 0)
                        return -1;
                    return value.Score;
                }
            }
            catch (ArgumentOutOfRangeException)
            {
            }
            return -1;

        }

        public IndicatorDataStruct GetCompanyIndicatorData(string indicatorNumber, Guid companyId)
        {
            if (string.IsNullOrEmpty(indicatorNumber))
            {
                return new IndicatorDataStruct();
            }

            var lowerIndicatorNumber = indicatorNumber.ToLowerInvariant();
            IndicatorDataStruct[] values;
            int index = GetIndicatorIndex(lowerIndicatorNumber);
            if (index < 0)
                return new IndicatorDataStruct();

            try
            {
                if (_indexIndicators.TryGetValue(companyId, out values))
                {
                    var value = values[index];
                    return value;
                }
            }
            catch (ArgumentOutOfRangeException)
            {
            }

            return new IndicatorDataStruct();
        }

        private int GetIndicatorIndex(string lowerIndicatorNumber)
        {
            int index;
            if (_indexIndicatorsCodes.TryGetValue(lowerIndicatorNumber, out index))
                return index;
            return -1;
        }


        public class ESGBestInClass
        {
            public float OverallBestInClass { get; set; }
            public float EBestInClass { get; set; }
            public float SBestInClass { get; set; }
            public float GBestInClass { get; set; }



            public float DisclosureMaxInGroup { get; set; }

            public float PreparednessMaxInGroup { get; set; }

            public float QualitativePerformanceMaxInGroup { get; set; }

            public float QuantitativePerformanceMaxInGroup { get; set; }
        }

        public class ESGAvg
        {
            public float OverallAvg { get; set; }
            public float EAvg { get; set; }
            public float SAvg { get; set; }
            public float GAvg { get; set; }
        }

        public class ESGData
        {
            public float Score { get; set; }

            public int Rank { get; set; }

            public float Percentile { get; set; }

            public int GroupCount { get; set; }

            public float EnvironmentScore { get; set; }

            public int EnvironmentRank { get; set; }

            public float EnvironmentPercentile { get; set; }


            public float SocialScore { get; set; }

            public int SocialRank { get; set; }

            public float SocialPercentile { get; set; }


            public float GovernanceScore { get; set; }

            public float GovernancePercentile { get; set; }

            public int GovernanceRank { get; set; }


            public float GroupAvg { get; set; }

            public float PreparednessScore { get; set; }

            public float DisclosureScore { get; set; }

            public float QualitativePerformanceScore { get; set; }

            public float QuantitativePerformanceScore { get; set; }

            public bool IsInReferenceUniverse { get; set; }
        }

        public struct ESGSlot
        {
            private float score;
            private int rank;
            private bool isInReferenceUniverse;
            public static ESGSlot Empty
            {
                get
                {
                    return new ESGSlot { Score = -1, Rank = -1 };
                }
            }

            public float Score { get => score; set => score = value; }
            public int Rank { get => rank; set => rank = value; }
            public bool IsInReferenceUniverse { get => isInReferenceUniverse; set => isInReferenceUniverse = value; }

            public bool Equals(ESGSlot other)
            {
                return Score.Equals(other.Score) && Rank == other.Rank;
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }
                return obj is ESGSlot && Equals((ESGSlot)obj);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    return (Score.GetHashCode() * 397) ^ Rank;
                }
            }
        }

        public struct IndicatorGroupStruct
        {
            public float GroupAvg { get; set; }

            public float BestPractice { get; set; }
        }

        public struct IndicatorDataStruct
        {
            public byte IsNotNan;
            public float Score;
            public double WeightedScore;
            public float Weight;
        }
    }


    public class CustomComparer : IComparer<KeyValuePair<int, double>>
    {
        public int Compare(KeyValuePair<int, double> x, KeyValuePair<int, double> y)
        {
            if (x.Key > y.Key)
                return 1;

            if (x.Key < y.Key)
                return -1;

            if (x.Value > y.Value)
                return 1;

            if (x.Value < y.Value)
                return -1;

            return 0;
        }
    }
}
